package com.zhuang.springbootnio.caffeine;

import com.github.benmanes.caffeine.cache.*;
import lombok.Data;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

public class ProductCache {
    // 缓存实例 (单例模式)
    private static final LoadingCache<Long, Product> cache = Caffeine.newBuilder()
            .maximumSize(10000)                     // 容量控制
            .expireAfterWrite(30, TimeUnit.MINUTES)  // 基础过期时间
            .refreshAfterWrite(5, TimeUnit.MINUTES)  // 自动刷新防雪崩
            .executor(Executors.newFixedThreadPool(4)) // 刷新线程池
            .recordStats()                           // 监控
            .build(new ProductLoader());             // 自定义加载逻辑

    // 防击穿锁 (Key级别细粒度锁)
    private static final ConcurrentMap<Long, AtomicBoolean> loadingLocks = new ConcurrentHashMap<>();

    // 缓存加载器 (核心防击穿逻辑)
    private static class ProductLoader implements CacheLoader<Long, Product> {
        @Override
        public Product load(Long productId) throws Exception {
            // 模拟数据库查询
            return fetchFromDB(productId);
        }

        @Override
        public Product reload(Long productId, Product oldValue) throws Exception {
            // 异步刷新时触发 (防雪崩关键)
            return fetchFromDB(productId);
        }

        private Product fetchFromDB(Long productId) {
            // 1. 获取Key专属锁 (防击穿)
            AtomicBoolean lock = loadingLocks.computeIfAbsent(productId, k -> new AtomicBoolean(false));
            while (!lock.compareAndSet(false, true)) {
                Thread.yield(); // 自旋等待
            }

            try {
                // 2. 再次检查缓存 (可能其他线程已加载)
                Product cached = cache.getIfPresent(productId);
                if (cached != null) return cached;

                // 3. 模拟数据库查询 (核心业务逻辑)
                System.out.println("Loading from DB: " + productId);
                Thread.sleep(100); // 模拟IO延迟
                return new Product(productId, "Product-" + productId, ThreadLocalRandom.current().nextInt(100));

            } catch (Exception e) {
                throw new RuntimeException("DB error", e);
            } finally {
                lock.set(false); // 释放锁
                loadingLocks.remove(productId); // 防止内存泄漏
            }
        }
    }

    // 商品类
    @Data
    public static class Product {
        private final Long id;
        private final String name;
        private final int stock;

        public Product(Long id, String name, int stock) {
            this.id = id;
            this.name = name;
            this.stock = stock;
        }
    }

    // 获取商品 (对外接口)
    public static Product getProduct(Long productId) {
        return cache.get(productId);
    }

    // 测试
    public static void main(String[] args) throws InterruptedException {
        // 模拟并发请求
        ExecutorService pool = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 100; i++) {
            long productId = i % 10; // 10个商品模拟热点
            pool.execute(() -> {
                Product p = getProduct(productId);
                System.out.println("Get: " + p.name);
            });
        }
        pool.shutdown();
        pool.awaitTermination(1, TimeUnit.SECONDS);

        // 打印统计
        System.out.println("缓存命中率: " + cache.stats().hitRate());
    }
}
